package ljl.bilibili.video.service.audience_reactions.play.impl;

import com.github.yulichang.wrapper.MPJLambdaWrapper;
import ljl.bilibili.client.pojo.RecommendVideo;
import ljl.bilibili.entity.user_center.user_info.User;
import ljl.bilibili.entity.video.video_production.upload.Video;
import ljl.bilibili.entity.video.video_production.upload.VideoData;
import ljl.bilibili.mapper.GetUserToVideoRecordMapper;
import ljl.bilibili.mapper.video.video_production.upload.VideoMapper;
import ljl.bilibili.video.service.audience_reactions.play.RecommendService;
import ljl.bilibili.video.vo.response.audience_reactions.play.UserToVideoRecord;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 推荐算法
 */
@Service
public class RecommendServiceImpl implements RecommendService {
    @Resource
    GetUserToVideoRecordMapper getUserToVideoRecordMapper;
    @Resource
    VideoMapper videoMapper;

    Map<Integer,Integer> itemToIdMap = new HashMap<>();//视频id对应顺序id
    Map<Integer,Integer> idToItemMap = new HashMap<>();//顺序id对应视频id
    Map<Integer, Map<Integer, Double>> itemMap = new HashMap<>(); //针对每个视频，存储所有用户对该视频的操作，播放1，点赞2，收藏4

    Map<Integer, Double> userMap = new HashMap<>(); //针对当前要推荐的用户，记录该用户对于视频的操作
    double[][] simMatrix; //产品之间的相似矩阵
    int TOP_K = 25;  //选择的相似item的数量
    int TOP_N = 20;  //定义最长推荐列表

    @Override
    public List<RecommendVideo> recommendVideo(int userId) {
        fillMap(userId);
        itemSimilarity();
        Set<Integer> videoIds = recommend();
        List<RecommendVideo> list;
        if (videoIds == null || videoIds.isEmpty()) return null;
        MPJLambdaWrapper<Video> wrapper = new MPJLambdaWrapper<>();
        wrapper.in(Video::getId, videoIds);
        wrapper.leftJoin(User.class, User::getId, Video::getUserId);
        wrapper.leftJoin(VideoData.class, VideoData::getVideoId, Video::getId);
        wrapper.select(Video::getCover, Video::getIntro, Video::getCreateTime, Video::getLength, Video::getUrl);
        wrapper.select(VideoData::getDanmakuCount, VideoData::getPlayCount, VideoData::getCollectCount);
        wrapper.selectAs(Video::getName, RecommendVideo::getVideoName);
        wrapper.selectAs(User::getNickname, RecommendVideo::getAuthorName);
        wrapper.selectAs(Video::getId, RecommendVideo::getVideoId);
        wrapper.selectAs(User::getId, RecommendVideo::getAuthorId);
        list = videoMapper.selectJoinList(RecommendVideo.class, wrapper);
        return list;
    }

    /**
     * 填充每个产品不同用户的评分，当前用户对于产品的评分
     * @param user
     */
    public void fillMap(int user) {
        List<UserToVideoRecord> list = getUserToVideoRecordMapper.getUserToVideoRecord();
        int itemId = 0; //产品计数
        for (UserToVideoRecord record : list) {
            if (!itemToIdMap.containsKey(record.getVideoId())) {
                Map<Integer, Double> currentUserMap = new HashMap<>();
                int userId = record.getUserId();
                currentUserMap.put(userId, 1.0);
                if (record.getLikedId() != null) {
                    currentUserMap.put(userId, currentUserMap.get(userId) + 2.0);
                }
                if (record.getCollectId() != null) {
                    currentUserMap.put(userId, currentUserMap.get(userId) + 4.0);
                }
                itemMap.put(record.getVideoId(), currentUserMap);
                idToItemMap.put(itemId, record.getVideoId());
                itemToIdMap.put(record.getVideoId(), itemId);
                itemId++;
            } else {
                Map<Integer, Double> currentUserMap = itemMap.get(record.getVideoId());
                int userId = record.getUserId();
                currentUserMap.put(userId, 1.0);
                if (record.getLikedId() != null) {
                    currentUserMap.put(userId, currentUserMap.get(userId) + 2.0);
                }
                if (record.getCollectId() != null) {
                    currentUserMap.put(userId, currentUserMap.get(userId) + 4.0);
                }
                itemMap.put(record.getVideoId(), currentUserMap);
            }
            if (record.getUserId() == user) {
                userMap.put(record.getVideoId(), 1.0);
                if (record.getLikedId() != null) {
                    userMap.put(record.getVideoId(), userMap.get(record.getVideoId()) + 2.0);
                }
                if (record.getCollectId() != null) {
                    userMap.put(record.getVideoId(), userMap.get(record.getVideoId()) + 4.0);
                }
            }
        }
    }

    /**
     * 计算相似矩阵
     */
    public void itemSimilarity() {
        simMatrix = new double[itemMap.size()][itemMap.size()];
        for (Map.Entry<Integer, Map<Integer, Double>> itemEntry1 : itemMap.entrySet()) {
            Set<Integer> ratedUserSet1 = itemEntry1.getValue().keySet();
            int ratedUserSize1 = ratedUserSet1.size();
            for (Map.Entry<Integer, Map<Integer, Double>> itemEntry2 : itemMap.entrySet()) {
                if (itemToIdMap.get(itemEntry2.getKey()) > itemToIdMap.get(itemEntry1.getKey())) {
                    Set<Integer> ratedUserSet2 = itemEntry2.getValue().keySet();
                    int ratedUserSize2 = ratedUserSet2.size();

                    int sameUserSize = interCount(ratedUserSet1, ratedUserSet2);
                    double similarity = sameUserSize / (Math.sqrt(ratedUserSize1 * ratedUserSize2));

                    simMatrix[itemToIdMap.get(itemEntry1.getKey())][itemToIdMap.get(itemEntry2.getKey())] = similarity;
                    simMatrix[itemToIdMap.get(itemEntry2.getKey())][itemToIdMap.get(itemEntry1.getKey())] = similarity;
                }
            }
        }
    }

    public Set<Integer> recommend() {
        //根据item相似度获取每个item最相似的TOP_K个产品
        Map<Integer, HashSet<Integer>> nearestItemMap = new HashMap<>();

        for(int i = 0;i<itemMap.size();i++) {
            Map<Integer, Double> simMap = new HashMap<>();
            for(int j = 0;j<itemMap.size();j++) {
                simMap.put(j,simMatrix[i][j]);
            }

            //对产品相似性进行排序
            simMap = sortMapByValues(simMap);

            int simItemCount = 0;
            HashSet<Integer> nearestItemSet = new HashSet<>();
            for(Map.Entry<Integer, Double> entry : simMap.entrySet()) {
                if(simItemCount<TOP_K) {
                    nearestItemSet.add(entry.getKey()); //获取相似itemID存入集合中
                    simItemCount++;
                }else
                    break;
            }
            //相似物品结果存入map中
            nearestItemMap.put(i,nearestItemSet);
        }

        Set<Integer> currentUserSet = new HashSet<>();
        for (int item : userMap.keySet()) {
            currentUserSet.add(itemToIdMap.get(item));
        }
        Map<Integer,Double> preRatingMap = new HashMap<>();
        for(int j = 0;j<itemMap.size();j++) {
            double preRating = 0;
            double sumSim = 0;
            //首先判断用户购买的列表中是否包含当前商品，如果包含直接跳过
            if (currentUserSet.contains(j)) continue;
            //判断当前产品的近邻中是否包含这个产品
            Set<Integer> interSet = interSet(currentUserSet, nearestItemMap.get(j));//获取当前用户的购买列表与产品相似品的交集

            //如果交集为空，则该产品预测评分为0
            if (!interSet.isEmpty()) {
                for (int id : interSet)  {
                    sumSim += simMatrix[j][id];
                    preRating += simMatrix[j][id] * userMap.get(idToItemMap.get(id));
                }
                if(sumSim != 0) preRating = preRating/sumSim; //如果相似性之和不为0计算得分，否则得分为0
                else preRating = 0;
            }
            preRatingMap.put(idToItemMap.get(j), preRating);
        }
        preRatingMap = sortMapByValues(preRatingMap);
        return preRatingMap.keySet();
    }

    //求两个集合交集的数量
    public int interCount(Set<Integer> set_a,Set<Integer> set_b) {
        int samObj = 0;
        for(Object obj : set_a) {
            if(set_b.contains(obj))
                samObj++;
        }
        return samObj;
    }
    //求两个集合交集
    public Set<Integer> interSet(Set<Integer> set_a,Set<Integer> set_b) {
        Set<Integer> tempSet = new HashSet<>();
        for(Integer obj:set_a) {
            if(set_b.contains(obj))
                tempSet.add(obj);
        }
        return tempSet;
    }

    //对map进行从大到小排序
    public <K extends Comparable, V extends Comparable> Map<K, V> sortMapByValues(Map<K, V> aMap) {
        HashMap<K, V> finalOut = new LinkedHashMap<>();
        aMap.entrySet().stream().sorted((p1, p2) -> p2.getValue().compareTo(p1.getValue())).collect(Collectors.toList())
                .forEach(ele -> finalOut.put(ele.getKey(), ele.getValue()));
        return finalOut;
    }
}
